﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Text;
using System.Web.Mvc;
using System.Web.Mvc.Html;
using MvcExtensions.Extensions;

namespace MvcExtensions.Controls.Grid
{


    #region Grid

    #region general columns

    public class GridColumn
    {
        public string HeaderText { get; set; }

        public string DataText { get; set; }

        public IDictionary<string, object> HtmlAttributes { get; set; }

        public IDictionary<string, object> CellHtmlAttributes { get; set; }

        public virtual string Render()
        {
            return DataText;
        }

        public virtual string RenderHeader()
        {
            return HeaderText;
        }
    }

    public class GridColumn<TModel> : GridColumn
    {
        public GridColumn(Func<TModel, MvcHtmlString> dataFunction)
        {
            DataFunction = dataFunction;
        }

        public Func<TModel, MvcHtmlString> DataFunction { get; protected set; }

        public HtmlHelper<TModel> HtmlHelper { get; set; }

        public ViewDataDictionary<TModel> ViewData { get; set; }

        public TModel Model { get { return ViewData.Model; } }

        public override string Render()
        {
            var value = DataFunction(Model);
            return value != null ? value.ToHtmlString() : string.Empty;
        }
    }

    public class GridColumn<TModel, TValue> : GridColumn
    {
        public GridColumn(Func<TModel, TValue, string> formatter = null,
            Func<TValue, string> valueFormatter = null)
        {
            Formatter = formatter;
            ValueFormatter = valueFormatter;
        }

        protected ModelMetadata GetModelMetadata()
        {
            return ModelMetadata.FromLambdaExpression(ColumnExpression, ViewData);
        }

        readonly object _defaultValue = default(TValue);

        protected TValue GetValue(ModelMetadata metadata = null)
        {
            var modelMetadata = metadata ?? GetModelMetadata();
            return (TValue)(modelMetadata.Model ?? _defaultValue);
        }

        public Func<TModel, TValue, string> Formatter { get; protected set; }
        public Func<TValue, string> ValueFormatter { get; protected set; }

        public Expression<Func<TModel, TValue>> ColumnExpression { get; set; }

        public HtmlHelper<TModel> HtmlHelper { get; set; }

        public ViewDataDictionary<TModel> ViewData { get; set; }

        public TModel Model { get { return ViewData.Model; } }

        public override string Render()
        {
            var value = HtmlHelper.DisplayFor(ColumnExpression, HtmlAttributes);
            var attributes = KnockoutControlExtensions.ConvertDictionaryToAttributes(HtmlAttributes);
            return string.Format("<span {1}>{0}</span>", value, attributes);
        }

        public override string RenderHeader()
        {
            if (!string.IsNullOrEmpty(HeaderText))
                return HeaderText;

            var modelMetadata = GetModelMetadata();
            return modelMetadata.DisplayName ?? modelMetadata.PropertyName;
        }
    }

    public class GridTextColumn<TModel, TValue> : GridColumn<TModel, TValue>
    {
        public override string Render()
        {
            return HtmlHelper.TextBoxFor(ColumnExpression, HtmlAttributes).ToHtmlString();
        }
    }

    public class GridCheckBoxColumn<TModel> : GridColumn<TModel, bool>
    {
        public override string Render()
        {
            return HtmlHelper.CheckBoxFor(ColumnExpression, HtmlAttributes).ToHtmlString();
        }
    }

    public class GridDropDownColumn<TModel, TValue> : GridColumn<TModel, TValue>
    {
        public override string Render()
        {
            return HtmlHelper.DropDownListFor(ColumnExpression, HtmlAttributes).ToHtmlString();
        }
    }

    #endregion

    public class Grid<TModel>
    {
        protected readonly List<GridColumn> InternalColumns = new List<GridColumn>();

        public IEnumerable<TModel> Model { get; private set; }

        public ViewDataDictionary<TModel> ViewData { get; set; }

        public HtmlHelper<TModel> HtmlHelper { get; private set; }

        public Grid(IEnumerable<TModel> model, HtmlHelper<TModel> htmlHelper)
        {
            Model = model;
            HtmlHelper = htmlHelper;
            ViewData = htmlHelper.ViewData; //new ViewDataDictionary<TModel>();
        }

        public IEnumerable<GridColumn> Columns
        {
            get { return InternalColumns; }
        }

        #region add columns

        public void AddColumn(string dataText, string headerText)
        {
            var column = new GridColumn
            {
                DataText = dataText,
                HeaderText = headerText
            };
            InternalColumns.Add(column);
        }

        public void AddColumn(Func<TModel, MvcHtmlString> dataFunc, string headerText)
        {
            var column = new GridColumn<TModel>(dataFunc)
            {
                HtmlHelper = HtmlHelper,
                ViewData = ViewData,
                HeaderText = headerText
            };
            InternalColumns.Add(column);
        }

        public GridColumn<TModel, TValue> AddColumnFor<TValue>(Expression<Func<TModel, TValue>> expr,
                                                               string headerText = null,
                                                               object htmlAttributes = null,
                                                               object cellHtmlAttributes = null)
        {
            var column = new GridColumn<TModel, TValue>
            {
                ColumnExpression = expr,
                HeaderText = headerText
            };
            AddColumn(column, htmlAttributes, cellHtmlAttributes);
            return column;
        }

        public GridTextColumn<TModel, TValue> AddTextColumnFor<TValue>(Expression<Func<TModel, TValue>> expr,
                                                                       string headerText = null,
                                                                       object htmlAttributes = null,
                                                                       object cellHtmlAttributes = null)
        {
            var column = new GridTextColumn<TModel, TValue>
            {
                ColumnExpression = expr,
                HeaderText = headerText
            };
            AddColumn(column, htmlAttributes, cellHtmlAttributes);
            return column;
        }

        public GridCheckBoxColumn<TModel> AddCheckBoxColumnFor(Expression<Func<TModel, bool>> expr,
                                                               string headerText = null,
                                                               object htmlAttributes = null,
                                                               object cellHtmlAttributes = null)
        {
            var column = new GridCheckBoxColumn<TModel>
            {
                ColumnExpression = expr,
                HeaderText = headerText
            };
            AddColumn(column, htmlAttributes, cellHtmlAttributes);
            return column;
        }

        public GridDropDownColumn<TModel, TValue> AddDropDownColumnFor<TValue>(Expression<Func<TModel, TValue>> expr,
                                                                               string headerText = null,
                                                                               object htmlAttributes = null,
                                                                               object cellHtmlAttributes = null)
        {
            var column = new GridDropDownColumn<TModel, TValue>
            {
                ColumnExpression = expr,
                HeaderText = headerText
            };
            AddColumn(column, htmlAttributes, cellHtmlAttributes);
            return column;
        }

        #endregion

        #region protected members

        protected void AddColumn<TValue>(GridColumn<TModel, TValue> column,
                                         object htmlAttributes = null,
                                         object cellHtmlAttributes = null)
        {
            column.HtmlHelper = HtmlHelper;
            column.ViewData = ViewData;
            column.HtmlAttributes = ControlExtensions.GetAttributeList(htmlAttributes);
            column.CellHtmlAttributes = ControlExtensions.GetAttributeList(cellHtmlAttributes);
            InternalColumns.Add(column);
        }

        protected ModelMetadata GetModelMetadata<TValue>(Expression<Func<TModel, TValue>> expression)
        {
            return ModelMetadata.FromLambdaExpression(expression, ViewData);
        }

        protected const string TableTag = "\n\t<table style=\"width: 100%\" class=\"gridTable\">{0}\n\t</table>";

        protected const string TableHeaderTag = "\n\t\t<thead>\n\t\t\t<tr class=\"gridHeader\">";
        protected const string TableHeaderTagEnd = "\n\t\t\t</tr>\n\t\t</thead>";

        protected const string TableRowTag = "\n\t\t\t<tr class=\"gridRow\">";
        protected const string TableRowTagEnd = "\n\t\t\t</tr>";

        protected const string TableBodyTag = "\n\t\t<tbody>{0}\n\t\t</tbody>";
        protected const string TableDataTag = "\n\t\t\t\t<td{0}>{1}</td>";
        protected const string TableTHTag = "\n\t\t\t\t<th{0}>{1}</th>";

        protected virtual string GetHeaderData()
        {
            var headerData = new StringBuilder(TableHeaderTag, Columns.Count() * 20);

            foreach (var column in Columns)
            {
                var attributes = column.CellHtmlAttributes != null
                                     ? KnockoutControlExtensions.ConvertDictionaryToAttributes(column.CellHtmlAttributes)
                                     : string.Empty;

                headerData.AppendFormat(TableTHTag, attributes, column.RenderHeader());
            }

            headerData.Append(TableHeaderTagEnd);
            return headerData.ToString();
        }

        protected virtual string GeRowData(TModel model)
        {
            var rowData = new StringBuilder(1024);
            rowData.Append(TableRowTag);
            ViewData.Model = model;

            foreach (var column in InternalColumns)
            {
                var attributes = column.CellHtmlAttributes != null
                     ? KnockoutControlExtensions.ConvertDictionaryToAttributes(column.CellHtmlAttributes)
                     : string.Empty;

                rowData.AppendFormat(TableDataTag, attributes, column.Render());
            }

            rowData.Append(TableRowTagEnd);
            return rowData.ToString();
        }

        protected virtual string GetBodyData()
        {
            var bodyData = new StringBuilder(1024);

            foreach (var model in Model)
            {
                var rowData = GeRowData(model);

                bodyData.Append(rowData);
            }

            return string.Format(TableBodyTag, bodyData);
        }

        #endregion

        public virtual MvcHtmlString ToHtmlString()
        {
            var headerData = GetHeaderData();

            var bodyData = GetBodyData();

            var tableHtmlData = string.Format(TableTag, headerData + bodyData);

            return MvcHtmlString.Create(tableHtmlData);
        }


        public override string ToString()
        {
            return ToHtmlString().ToHtmlString();
        }
    }

    #endregion
    #region Knockout Grid

    public class KOGrid<TModel> : Grid<TModel>
    {
        protected const string KOTableBodyTag = "\n\t\t<tbody data-bind=\"foreach: {0}\">{1}\n\t\t</tbody>";

        public KOGrid(IEnumerable<TModel> model, HtmlHelper<TModel> htmlHelper, string propertyName)
            : base(model, htmlHelper)
        {
            PropertyName = propertyName;
        }

        public string PropertyName { get; protected set; }

        #region add KO Columns

        public GridColumn<TModel, TValue> AddKOColumnFor<TValue>(Expression<Func<TModel, TValue>> expr,
                                                                 string headerText = null,
                                                                 object htmlAttributes = null,
                                                                 object cellHtmlAttributes = null,
                                                                 KOBindingOptions cellBindingOptions = null)
        {
            var column = AddColumnFor(expr, headerText, htmlAttributes, cellHtmlAttributes);
            var metadata = GetModelMetadata(expr);
            column.HtmlAttributes.Add("data-bind", "text:" + metadata.PropertyName);
            SetCellBinding(cellBindingOptions, column);
            return column;
        }

        public GridTextColumn<TModel, TValue> AddKOTextColumnFor<TValue>(Expression<Func<TModel, TValue>> expr,
                                                                         string headerText = null,
                                                                         object htmlAttributes = null,
                                                                         object cellHtmlAttributes = null,
                                                                         KOBindingOptions cellBindingOptions = null)
        {
            var column = AddTextColumnFor(expr, headerText, htmlAttributes, cellHtmlAttributes);

            var metadata = GetModelMetadata(expr);
            column.HtmlAttributes.Add("data-bind", "value:" + metadata.PropertyName);
            SetCellBinding(cellBindingOptions, column);
            return column;
        }

        private const string KODateBining = "dateString: {0}, showDatePicker : true";

        public GridTextColumn<TModel, TValue> AddKODateTextColumnFor<TValue>(Expression<Func<TModel, TValue>> expr,
                                                                             string headerText = null,
                                                                             object htmlAttributes = null,
                                                                             object cellHtmlAttributes = null,
                                                                             KOBindingOptions cellBindingOptions = null)
        {
            var column = AddTextColumnFor(expr, headerText, htmlAttributes, cellHtmlAttributes);

            var metadata = GetModelMetadata(expr);
            column.HtmlAttributes.Add("data-bind", string.Format(KODateBining, metadata.PropertyName));
            SetCellBinding(cellBindingOptions, column);
            return column;
        }

        public GridCheckBoxColumn<TModel> AddKOCheckBoxColumnFor(Expression<Func<TModel, bool>> expr,
                                                                 string headerText = null,
                                                                 object htmlAttributes = null,
                                                                 object cellHtmlAttributes = null,
                                                                 KOBindingOptions cellBindingOptions = null)
        {
            var column = AddCheckBoxColumnFor(expr, headerText, htmlAttributes, cellHtmlAttributes);

            var metadata = GetModelMetadata(expr);
            column.HtmlAttributes.Add("data-bind", "checked:" + metadata.PropertyName);
            SetCellBinding(cellBindingOptions, column);
            return column;
        }

        public GridDropDownColumn<TModel, TValue> AddKODropDownColumnFor<TValue>(Expression<Func<TModel, TValue>> expr,
                                                                                 string headerText = null,
                                                                                 object htmlAttributes = null,
                                                                                 object cellHtmlAttributes = null,
                                                                                 KOBindingOptions cellBindingOptions = null)
        {
            var column = AddDropDownColumnFor(expr, headerText, htmlAttributes, cellHtmlAttributes);

            var metadata = GetModelMetadata(expr);
            column.HtmlAttributes.Add("data-bind", "value:" + metadata.PropertyName);

            SetCellBinding(cellBindingOptions, column);
            return column;
        }

        private static void SetCellBinding(KOBindingOptions cellBindingOptions, GridColumn column)
        {
            if (cellBindingOptions != null)
                column.CellHtmlAttributes.Add("data-bind", cellBindingOptions.ToParams());
        }

        #endregion


        protected override string GetBodyData()
        {
            var bodyData = new StringBuilder(1024);
            //create only body template for knockout
            var model = Activator.CreateInstance<TModel>();
            var rowData = GeRowData(model);
            bodyData.Append(rowData);

            return string.Format(KOTableBodyTag, PropertyName, bodyData);
        }

        public override MvcHtmlString ToHtmlString()
        {
            var headerData = GetHeaderData();

            var bodyData = GetBodyData();

            //use KOTableTag for KO binding
            var tableHtmlData = string.Format(TableTag, headerData + bodyData);

            return MvcHtmlString.Create(tableHtmlData);
        }
    }

    #region KO columns

    #endregion

    #endregion


}
